import { makeAutoObservable, toJS } from 'mobx';
import { v4 as uuidv4 } from 'uuid';
import Fuse from 'fuse.js';
import { Commands as _Commands, ArgTypes as _ArgTypes, TargetTypes, registerCommand } from '../../side-model';
const EventEmitter = require('events');
import { mergeEventEmitter } from '@/common/events';

const DEFAULT_NEW_WINDOW_TIMEOUT = 2000;
const EE = Symbol('event-emitter');

// test.command 项
export default class Command {
    id = null;

    comment = '';

    command;

    target;

    targets = [];

    value;

    isBreakpoint = false;

    opensWindow = false;

    windowHandleName = '';

    windowTimeout = DEFAULT_NEW_WINDOW_TIMEOUT;

    opensWindowRead = false;

    constructor(id = uuidv4(), command, target, value) {
        this.id = id;
        this.command = command || '';
        this.target = target || '';
        this.value = value || '';
        this.export = this.export.bind(this);
        this[EE] = new EventEmitter();
        mergeEventEmitter(this, this[EE]);

        makeAutoObservable(this, {}, { autoBind: true });
    }

    get displayedName() {
        return this.enabled ? this.command : this.command.substr(2);
    }

    get enabled() {
        return this.command && !this.command.startsWith('//');
    }

    get isValid() {
        return Commands.array.includes(this.command);
    }

    // 定位器才需要targets
    get canHaveTargets() {
        if (Commands.list.has(this.command)) {
            const { type } = Commands.list.get(this.command);
            return type === TargetTypes.LOCATOR;
        }
        return false;
    }

    clone() {
        const clone = new Command();
        clone.setData(this.export());
        return clone;
    }

    setComment(comment) {
        this.comment = comment || '';
    }

    setCommand(command) {
        if (Commands.values[command]) {
            this.command = Commands.values[command];
        } else {
            this.command = command || '';
        }

        if (!this.canHaveTargets) {
            this.setTargets();
        }

        if (this.command.indexOf('waitFor') > -1 && this.value === '') {
            this.setValue('30000');
        }
    }

    setTarget(target) {
        this.target = target || '';
    }

    setTargets(targets = []) {
        if (this.canHaveTargets) {
            this.targets.replace(targets);
        } else {
            this.targets.replace([]);
        }
    }

    setValue(value) {
        this.value = value ? value.replace(/\n/g, '\\n') : '';
    }

    setOpensWindow(opensWindow) {
        if (typeof opensWindow == typeof true) {
            this.opensWindow = opensWindow;
            if (!this.windowHandleName) {
                this.windowHandleName = `win${Math.floor(Math.random() * 10000)}`;
            }
        }
    }

    setWindowHandleName(newName) {
        const oldName = this.windowHandleName;
        this.windowHandleName = newName;
        this[EE].emit('window-handle-name-changed', oldName, newName);
    }

    toggleOpensWindowRead() {
        this.opensWindowRead = !this.opensWindowRead;
    }

    setWindowTimeout(timeout) {
        this.windowTimeout = timeout;
    }

    toggleBreakpoint() {
        this.isBreakpoint = !this.isBreakpoint;
    }

    toggleEnabled() {
        if (this.enabled) {
            this.setCommand('//' + this.command);
        } else {
            this.setCommand(this.command.substr(2));
        }
    }

    setData(jsRep) {
        this.setComment(jsRep.comment);
        this.setCommand(jsRep.command);
        this.setTarget(jsRep.target);
        this.setTargets(jsRep.targets);
        this.setValue(jsRep.value);

        if (jsRep.opensWindow) {
            this.setOpensWindow(jsRep.opensWindow);
            this.setWindowHandleName(jsRep.windowHandleName);
            this.setWindowTimeout(jsRep.windowTimeout);
        }
    }

    export() {
        let exported = {
            id: this.id,
            comment: this.comment,
            command: this.command,
            target: this.target,
            targets: toJS(this.targets),
            value: this.value,
        };

        if (this.opensWindow) {
            exported.opensWindow = this.opensWindow;
            exported.windowHandleName = this.windowHandleName;
            exported.windowTimeout = this.windowTimeout;
        }

        return exported;
    }

    static fromJS = function (jsRep) {
        const command = new Command(jsRep.id);
        command.setData(jsRep);
        return command;
    };
}

export const ArgTypes = _ArgTypes;

class CommandList {
    list = new Map(_Commands);

    get array() {
        return Array.from(this.list.keys());
    }

    get values() {
        return this.array.reduce((commands, command) => {
            commands[this.list.get(command).name] = command;
            return commands;
        }, {});
    }

    get fuse() {
        return new Fuse(Array.from(this.list.values()), {
            shouldSort: true,
            threshold: 0.4,
            location: 0,
            distance: 100,
            maxPatternLength: 50,
            minMatchCharLength: 1,
            keys: ['name'],
        });
    }

    search(pattern) {
        return this.fuse.search(pattern);
    }

    addCommand(id, name) {
        if (this.list.has(id)) {
            throw new Error(`Command with the id ${id} already exists`);
        } else {
            this.list.set(id, name);
            registerCommand(id, name);
        }
    }

    static get instance() {
        if (!this._instance) {
            this._instance = new CommandList();
        }
        return this._instance;
    }
}

export const Commands = CommandList.instance;

export const ControlFlowCommandNames = {
    do: 'do',
    else: 'else',
    elseIf: 'elseIf',
    end: 'end',
    forEach: 'forEach',
    if: 'if',
    repeatIf: 'repeatIf',
    times: 'times',
    while: 'while',
};

function commandNamesEqual(command, target) {
    if (command) {
        return command.command === target;
    } else {
        return false;
    }
}

function isBlockOpen(command) {
    return isIf(command) || isLoop(command);
}

function isConditional(command) {
    switch (command.command) {
        case ControlFlowCommandNames.elseIf:
        case ControlFlowCommandNames.if:
        case ControlFlowCommandNames.repeatIf:
        case ControlFlowCommandNames.times:
        case ControlFlowCommandNames.while:
            return true;
        default:
            return false;
    }
}

function isControlFlow(command) {
    switch (command.command) {
        case ControlFlowCommandNames.if:
        case ControlFlowCommandNames.elseIf:
        case ControlFlowCommandNames.else:
        case ControlFlowCommandNames.end:
        case ControlFlowCommandNames.do:
        case ControlFlowCommandNames.repeatIf:
        case ControlFlowCommandNames.times:
        case ControlFlowCommandNames.while:
            return true;
        default:
            return false;
    }
}

function isDo(command) {
    return commandNamesEqual(command, ControlFlowCommandNames.do);
}

function isElse(command) {
    return commandNamesEqual(command, ControlFlowCommandNames.else);
}

function isElseIf(command) {
    return commandNamesEqual(command, ControlFlowCommandNames.elseIf);
}

function isElseOrElseIf(command) {
    return isElseIf(command) || isElse(command);
}

function isEnd(command) {
    return commandNamesEqual(command, ControlFlowCommandNames.end);
}

function isIf(command) {
    return commandNamesEqual(command, ControlFlowCommandNames.if);
}

function isIfBlock(command) {
    return isIf(command) || isElseOrElseIf(command);
}

function isLoop(command) {
    return (
        commandNamesEqual(command, ControlFlowCommandNames.while) ||
        commandNamesEqual(command, ControlFlowCommandNames.times) ||
        commandNamesEqual(command, ControlFlowCommandNames.repeatIf) ||
        commandNamesEqual(command, ControlFlowCommandNames.forEach)
    );
}

function isTerminal(command) {
    return isElse(command) || isDo(command) || isEnd(command);
}

function isTimes(command) {
    return commandNamesEqual(command, ControlFlowCommandNames.times);
}

function isForEach(command) {
    return commandNamesEqual(command, ControlFlowCommandNames.forEach);
}

export const ControlFlowCommandChecks = {
    isIfBlock: isIfBlock,
    isConditional: isConditional,
    isDo: isDo,
    isElse: isElse,
    isElseOrElseIf: isElseOrElseIf,
    isEnd: isEnd,
    isIf: isIf,
    isLoop: isLoop,
    isBlockOpen: isBlockOpen,
    isTerminal: isTerminal,
    isControlFlow: isControlFlow,
    isTimes: isTimes,
    isForEach: isForEach,
};
